Skip to content

Physics: a renderable that places itself collides at its pos - #1694

Merged
obiot merged 1 commit into
masterfrom
fix/anchor-opt-out
Sep 22, 2026
Merged

obiot merged 1 commit into
masterfrom
fix/anchor-opt-out

Conversation

@obiot

@obiot obiot commented Sep 22, 2026

Copy link
Copy Markdown
Member

Follow-up to #1693, which shipped a hole in its own fix, and a second defect it left behind.

A renderable that places itself

#1693 moved every backend onto the frame a renderable DRAWS in, subtracting size * anchorPoint from where a body is built. That assumed every renderable takes that offset, and the engine already carries a flag saying otherwise: applyAnchorTransform, which is what preDraw reads before shifting anything. GLTFModel clears it in its constructor, Mesh clears it on the Camera3d world-space path, both because they emit world coordinates and pivot about their own model origin rather than a bounds box. They draw at pos while their anchorPoint still holds the default (0.5, 0.5), where it means nothing.

Reading it anyway moved each of those bodies by half its OWN bounds box. A scene sizes that box per node, so two objects that overlap on screen were displaced by different amounts, and the contact between them was not merely shifted, it was never reported at all. The Jungle Rabbit example is built entirely from such renderables and lost its collisions completely.

The same condition now guards all five places that compute the offset:

file what it feeds
sat.js 2D narrowphase
sat3d.js Box3d narrowphase
builtin-adapter.ts getBodyShapes / getBodyAABB / raycast3d
matter-adapter/src/index.ts body construction
planck-adapter/src/index.ts body construction

The two external adapters are in scope despite never seeing a Box3d: the flag is a plain Renderable flag, so an ordinary Rect hitbox on such a renderable was mis-built there too. The parity case added to both is exactly that, and it fails on both without the fix.

raycast3d measured in a different frame

#1693 moved the narrowphase onto the drawn frame and left raycast3d building its world AABB from raw pos. For any anchored body the two then disagreed by half a renderable, so a floor probe placed a character on a surface the solver does not have there. Nothing failed, because every existing case in that spec was one body queried against itself and no test compared the query to the narrowphase. raycast3d-box3d.spec.js now authors in the collision frame and has a case pinning the two together.

Tests

Six new cases, each verified to fail with the defect put back:

  • box3d-world.spec.js — a renderable that places itself collides where it draws, and one that does not still reads its anchor (mutants in both directions killed)
  • builtin-resting.spec.js — the same contract through a full 2D drop
  • raycast3d-box3d.spec.js — a ray hits the box where the narrowphase collides with it
  • parity.spec.ts on both adapters — a Rect body on a renderable that places itself

The first draft of the Box3d pair was vacuous: sizing each box from its own renderable's bounds made the anchor shift scale with the box, so the contact survived it. The boxes are now sized independently of the bounds they hang off.

melonjs: 7148 passed. matter: 225. planck: 220. debug-plugin: 23.

Releases

@melonjs/matter-adapter 1.5.1 and @melonjs/planck-adapter 1.6.1, dated and ready to dispatch on merge, since 1.5.0 and 1.6.0 went to npm carrying this. melonjs 20.7.0 stays _unreleased_; its entry is amended rather than added to, since the hole never shipped there.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t

20.7 moved every backend onto the frame a renderable DRAWS in, subtracting
`size * anchorPoint` from where a body is built. That assumed every
renderable takes that offset, and the engine already has a flag saying
otherwise: `applyAnchorTransform`, which is what `preDraw` reads before
shifting anything. `GLTFModel` clears it in its constructor and `Mesh`
clears it on the `Camera3d` world-space path, because both emit world
coordinates and pivot about their own model origin rather than a bounds
box, so they draw at `pos` while their `anchorPoint` still holds the
default (0.5, 0.5), where it means nothing.

Reading it anyway moved each of those bodies by half its OWN bounds box.
A scene sizes that box per node, so two objects overlapping on screen were
displaced by different amounts and the contact between them was not merely
shifted, it was never reported at all. The same condition now guards all
five places that compute the offset: the 2D and 3D narrowphases, the
builtin adapter's geometry readback, and both external adapters, which see
this through any ordinary `Rect` on such a renderable and not only through
a `Box3d`.

`raycast3d` is fixed with it. 20.7 moved the narrowphase onto the drawn
frame and left `raycast3d` building its world AABB from raw `pos`, so a
floor probe and the solver disagreed by half a renderable on any anchored
body. Nothing caught that because no test compared the two, which is what
the new case in `raycast3d-box3d.spec.js` does.

Six regression tests, each verified to fail with the defect put back.
`matter-adapter` 1.5.1 and `planck-adapter` 1.6.1, since 1.5.0 and 1.6.0
went to npm carrying this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
Copilot AI lite review requested due to automatic review settings September 22, 2026 07:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@obiot
obiot merged commit b53c8d0 into master Sep 22, 2026
6 checks passed
@obiot
obiot deleted the fix/anchor-opt-out branch September 22, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants